#!/bin/bash -e
#
#   Neutron3529's Unity Game Plugin
#   Copyright (C) 2022 Neutron3529
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as
#   published by the Free Software Foundation, either version 3 of the
#   License, or (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
############################################################################
#
#   * compile instructions: put this file and `utils.cs` in `steamapps`
#   * folder, open a terminal in the same folder, and execute:
#   *
#   * ```
#   *     chmod +x ${file}.cs
#   *     ./${file}.cs
#   * ```
#   *
#   * then the mod will be compiled automatically.
#   *
#   * Here we wrote a shebang like file, which is correct
#   * in my computer (Manjaro XFCE), if such script do not work
#   * in your computer, you could just try the instructions below :

export GAME_NAME="${0%\.cs}"                                # might modify if the name mismatch.
export GAME_DIR="$GAME_NAME"                                # might be modified, but "$GAME_NAME" cover most of the cases.

export FILE_NAME="$0"
export ASSEMBLY="Assembly-CSharp"                           # might be modified
export UTILS="utils.cs"                                     # might be modified if you do not put utils.cs in the current dir.
export PLUGIN_ID="Neutron3529.Cheat"                        # should be modified
export NAMESPACE_ID="Neutron3529.Cheat"                     # should be modified
export GAME_BASE_DIR="common/$GAME_DIR"                     # should modify GAME_DIR instead since GAME_DIR == GAME_NAME is almost always true.

export IFS=$'\n' # to disable the annoying space.
export DOTNET="dotnet" # the location of the DOTNET executable file.
[ -z "$DOTNET_CSC_DLL" ] && export DOTNET_CSC_DLL=`\ls /usr/share/dotnet/sdk/*/Roslyn/bincore/csc.dll` # In manjaro, the csc.dll is located in /usr/share/dotnet/sdk/*/Roslyn/bincore/csc.dll

case $1 in
    V)       EXTRA_DEFINE="-define:DEBUG${IFS}-define:VERBOSE${IFS}-debug" ;;
    v)       EXTRA_DEFINE="-define:DEBUG${IFS}-define:VERBOSE${IFS}-debug" ;;
    VERBOSE) EXTRA_DEFINE="-define:DEBUG${IFS}-define:VERBOSE${IFS}-debug" ;;
    verbose) EXTRA_DEFINE="-define:DEBUG${IFS}-define:VERBOSE${IFS}-debug" ;;
    D)       EXTRA_DEFINE="-define:DEBUG${IFS}-debug"                      ;;
    d)       EXTRA_DEFINE="-define:DEBUG${IFS}-debug"                      ;;
    DEBUG)   EXTRA_DEFINE="-define:DEBUG${IFS}-debug"                      ;;
    debug)   EXTRA_DEFINE="-define:DEBUG${IFS}-debug"                      ;;
    *)       EXTRA_DEFINE=""                                               ;;
esac

_MODE__SELECT_=100

( yes "" | head -n $_MODE__SELECT_ | head -n-1  ; tail $FILE_NAME -n+$_MODE__SELECT_) | sed s/%%NAMESPACE_ID%%/${NAMESPACE_ID}/g | sed s/%%PLUGIN_ID%%/${PLUGIN_ID}/g | $DOTNET $DOTNET_CSC_DLL -nologo -t:library \
  -r:"${GAME_BASE_DIR}/BepInEx/core/0Harmony.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/core/BepInEx.Core.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/core/BepInEx.IL2CPP.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/core/UnhollowerBaseLib.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/unhollowed/Il2Cppmscorlib.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/unhollowed/Il2CppSystem.Core.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/unhollowed/UnityEngine.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/unhollowed/UnityEngine.UI.dll" \
  -r:"${GAME_BASE_DIR}/BepInEx/unhollowed/UnityEngine.CoreModule.dll" \
  $(for i in "${GAME_BASE_DIR}/BepInEx/unhollowed/$ASSEMBLY"*.dll ; do echo -e "-r:\"$i\"\n" ; done) \
  `[ -e "${GAME_BASE_DIR}/mono/Managed/netstandard.dll" ] && echo "-r:\"${GAME_BASE_DIR}/mono/Managed/netstandard.dll\""` \
  -r:"${GAME_BASE_DIR}/mono/Managed/System.dll" \
  -r:"${GAME_BASE_DIR}/mono/Managed/System.Core.dll" \
  -r:"${GAME_BASE_DIR}/mono/Managed/mscorlib.dll" \
  -out:"${GAME_BASE_DIR}/BepInEx/plugins/${FILE_NAME%.*}".dll \
  -optimize $EXTRA_DEFINE \
  - $UTILS && rm -f "${GAME_BASE_DIR}/BepInEx/config/${PLUGIN_ID}.cfg";

if [ -n "$2" ]; then
    git add ${FILE_NAME}
    case $2 in
        R) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        r) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        RANDOM) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        random) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        U) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        u) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        UPLOAD) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        upload) git commit -am "`curl -s https://whatthecommit.com/index.txt`" ;;
        *) git commit -am "$2" ;;
    esac
    git push
fi
exit

// Affogato = 加把劲魔女


// #define ALL
using Il2CppSystem;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

namespace %%NAMESPACE_ID%%;

[BepInPlugin("%%PLUGIN_ID%%", "%%NAMESPACE_ID%%", "0.1.0")]
public class Cheat : Neutron3529.ModEntry {
    public Cheat() : base("%%PLUGIN_ID%%") {}
    public override void Awake() {
        base.Awake();
        // Mafi.Localization.LocalizationManager.EnableLocalization(); // 不知道为什么不加这一句会导致游戏变成纯英文
        // 主要逻辑放在`utils.cs`中，这里的Awake2只是为了以防万一
        // 目前来说，这个函数的唯一用途是用来“叮”……
        // 就像这样：
        logger("叮~修改器启动，请安心游戏");
    }
    // [Desc("Debug: CardBase - SkillInit无法patch")]
    // [HarmonyPatch(typeof(CardBase),"SkillInit")]
    // class test:Entry {
    //     static int count=0;
    //     public static void Prefix(MethodBase __originalMethod) {
    //         // count++;
    //         // logger($"Call {count} {__originalMethod.DeclaringType} - {__originalMethod.Name} ({__originalMethod})");
    //     }
    //     // public static void Finalizer(MethodBase __originalMethod) {
    //     //     logger($"Done {count} {__originalMethod.DeclaringType} - {__originalMethod.Name} ({__originalMethod})");
    //     //     count++;
    //     // }
    // }
//     [Desc("调用关系追踪器")]
//     class Scanner : Entry {
//         static int count=0;
//         static System.Type type_of_class_in_the_scanning_assembly = typeof(SkillRunTime);
//         // public override void Enable(){
//         //     var methods=TargetMethods();
//         //     foreach(var i in methods){
//         //         logger($"开始手工注入 {i}");
//         //         ModEntry.harmony.Patch(i, new HarmonyMethod(typeof(Scanner).GetMethod("Prefix")), new HarmonyMethod(typeof(Scanner).GetMethod("Postfix")));
//         //         logger($"完成手工注入 {i}");
//         //     }
//         // }
//         static IEnumerable<MethodBase> TargetMethods() {
//             HashSet<MethodBase> hashset = new();
//             List<MethodBase> list =  new();
// #if ALL
//             System.Type[] patch_types=null;
//             if(type_of_class_in_the_scanning_assembly!=typeof(void)){
//                 patch_types=type_of_class_in_the_scanning_assembly.Assembly.GetTypes(); // would generate too much patcher, make harmony failed to patch.
//             }
// #else
//             var patch_types=new List<System.Type>();
//             patch_types.Add(typeof(SkillRunTime));
//             patch_types.Add(typeof(CharacterObject));
//             patch_types.Add(typeof(FightScript));
//             patch_types.Add(typeof(CardBase));
//             patch_types.Add(typeof(CharacterCardView));
//             patch_types.Add(typeof(CardSaveData));
//             patch_types.Add(typeof(CharacterRuntime));
// #endif
//             foreach(var ty in patch_types){
//                 MethodBase[] m=ty.GetMethods((BindingFlags)(-1));
//                 foreach(MethodBase i in m){
//                     if(i.DeclaringType==ty){
//                         bool getter_or_setter=(i.Name.Length>4 && ((i.Name[0]=='g' || i.Name[0]=='s') && i.Name[1]=='e' && i.Name[2]=='t' && i.Name[3]=='_'));
//                         bool monostart=typeof(MonoBehaviour).IsAssignableFrom(ty) && (i.Name == "Start" || i.Name == "Awake" || i.Name == "Update" || i.Name == "FixedUpdate" || i.Name == "DownUpdate" || i.Name == "LateUpdate");
//                         bool dispose = i.Name == "System_IDisposable_Dispose" || i.Name == "Dispose" || i == typeof(CardBase).GetMethod("SkillInit") || i == typeof(CharacterObject).GetMethod("Stop") || i.Name == "OnDestroy";
//                         if(i.IsGenericMethod || i.IsAbstract || i.IsVirtual || i.IsSpecialName || i.IsConstructor || getter_or_setter || monostart || dispose) {
//                             logger($"过滤{i.DeclaringType} - {i.Name}, 原因为 {i.IsGenericMethod} || {i.IsAbstract} || {i.IsSpecialName} || {i.IsConstructor} || {getter_or_setter} || {monostart} || {dispose}");
//                         } else if(hashset.Add(i)){
//                             list.Add(i);
//                         }
//                     }
//                 }
//             }
//             foreach(MethodBase i in list){
//                 // bool non_generic=!i.IsGenericMethod;
//                 logger("注入: "+i.DeclaringType+" - "+i);
//                 yield return i;
//             }
//         }
//         public static bool Prefix(object __instance, MethodBase __originalMethod) {
//             count++;
//             logger($"Call {count} {__originalMethod.DeclaringType} - {__originalMethod.Name} ({__originalMethod})");
//             return __instance!=null;
//         }
//         public static void Finalizer(MethodBase __originalMethod) {
//             logger($"Done {count} {__originalMethod.DeclaringType} - {__originalMethod.Name} ({__originalMethod})");
//             count--;
//         }
//     }

    // [Desc("技能攻击范围修改")]
    // [HarmonyPatch(typeof(SkillRunTime),"CheckAttackRange")]
    // class SkillRunTimeCheckAttackRange : Entry {
    //     [Desc("玩家技能攻击范围乘数")]
    //     static float pmul=2f;
    //     [Desc("NPC技能攻击范围乘数")]
    //     static float nmul=.5f;
    //     static void Prefix(SkillRunTime __instance, ref Vector2Int pos){
    //         if (__instance.IsPlayer){
    //             logger("SkillRunTimeCheckAttackRange-IsPlayer");
    //             pos.m_X=Mathf.CeilToInt(pos.m_X*pmul);
    //             pos.m_Y=Mathf.CeilToInt(pos.m_Y*pmul);
    //         } else {
    //             logger("SkillRunTimeCheckAttackRange-!IsPlayer");
    //             pos.m_X=Mathf.CeilToInt(pos.m_X*nmul);
    //             pos.m_Y=Mathf.CeilToInt(pos.m_Y*nmul);
    //         }
    //     }
    // }
    // [HarmonyPatch(typeof(FightScript),"ChangeAttackRange")]
    // class FightScriptChangeAttackRange : Entry {
    //     [Desc("玩家技能攻击范围乘数")]
    //     static float pmul=2f;
    //     static void Prefix(CharacterObject User, ref float range){
    //         logger("FightScriptChangeAttackRange");
    //         range*=pmul;
    //     }
    // }
    // [HarmonyPatch(typeof(CharacterObject),"AttackSpeed", MethodType.Getter)]
    // class CharacterObjectAttackSpeed : Entry {
    //     [Desc("玩家攻速乘数")]
    //     static float pmul=2f;
    //     static float Postfix(float ret){
    //         logger("Get FightScriptAttackSpeed");
    //         return ret*pmul;
    //     }
    // }
    [HarmonyPatch(typeof(LifeShopData),"BuyItem")]
    class LifeShopDataBuyItem : Entry {
        [Desc("物品价格上限（可以为零）")]
        static float price=0f;
        static void Prefix(ShopItemData item){
            item.price=Math.Max(item.price,price);

        }
    }
    [HarmonyPatch(typeof(CharacterObject),"Update")]
    class CharacterObjectAttackSpeed : Entry {
        [Desc("玩家恒定满HP（会导致主线卡关）")]
        static bool hp=false;
        [Desc("玩家攻速最小值")]
        static float speed=5;
        [Desc("玩家射程最小值")]
        static float range=10;
        [Desc("玩家防御最小值")]
        static float def=50;
        static void Prefix(CharacterObject __instance){
            if(__instance!=null && __instance.initOver){
                if(__instance.TeamId==0){
                    if(hp){
                        var res=__instance.MaxHealth-__instance.CurHealth;
                        if(res>0){
                            __instance.ChangeHealth=res;
                            // logger($"{__instance.Id.ToString()}({__instance.initTeamId} {__instance.InstanceId}) got {res} damage");
                            // logger($"status: atk {__instance.data.attack} def {__instance.data.physicsResistance} mdf {__instance.data.magicResistance} spd {__instance.data.attackSpeed} range {__instance.data.attackRange.distance}");
                            // [Info   :Neutron3529.Cheat] -1581992186(1 31e2ecf42e7344dcb021bff9c2c47f1e) got 80 damage
                            // [Info   :Neutron3529.Cheat] -1621470658(0 fd62ec74c6ae460fa67ede34d6557ccb) got 30 damage
                        }
                    }
                    if(__instance.data.attackSpeed<speed){
                        __instance.data.attackSpeed=speed;
                    }
                    if(__instance.data.attackRange.distance<range){
                        __instance.data.attackRange.distance=range;
                    }
                    if(__instance.data.physicsResistance<def){
                        __instance.data.physicsResistance=def;
                    }
                    if(__instance.data.magicResistance<def){
                        __instance.data.magicResistance=def;
                    }
                }
            }
        }
    }
}
